#include <asm/uaccess.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/list_sort.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <linux/slab.h>

/***************************************************************/
/*************************** Debug *****************************/
/***************************************************************/
/*
 * Error messages
 */
#define print_failed_exit(func)					\
	pr_err_once("FAILED:%s()\n", func);
#define print_error(...)					\
	do {							\
		pr_err_once("ERROR:");				\
		printk(__VA_ARGS__);				\
		printk("\n");					\
	} while (0)
/*
 * Start/Exit and LOG messages
 */
#define print_start(func)					\
	pr_debug("-->%s()\n", func);
#define print_exit(func)					\
	pr_debug("<--%s()\n", func);
#define print_log(...)						\
	do {							\
		pr_debug("LOG: ");				\
		pr_debug(__VA_ARGS__);				\
		pr_debug("\n");					\
	} while (0)

/***************************************************************/
/******************** Init/destroy functions *******************/
/***************************************************************/
static int __init snapshot_init(void);
static void __exit snapshot_exit(void);

/***************************************************************/
/****************** /proc/ file handlers ***********************/
/***************************************************************/
#define PROC_NAME		"snapshot"
static struct proc_dir_entry	*proc;

static int snapshot_open(struct inode * , struct file *);
static ssize_t snapshot_write(struct file * , const char * , unsigned long ,
			      loff_t *);
/*
 * Available file operations
 */
static const struct file_operations proc_fops = {
	.owner		= THIS_MODULE,
	.open		= snapshot_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= seq_release,
	.write		= snapshot_write
};
/*
 * seq_file interface
 */
static void *snapshot_seq_start(struct seq_file * , loff_t *);
static void *snapshot_seq_next(struct seq_file * , void * , loff_t *);
static void snapshot_seq_stop(struct seq_file * , void *);
static int snapshot_seq_show(struct seq_file * , void *);
/*
 * Available seq_file operations
 */
static const struct seq_operations snapshot_ops = {
	.start		= snapshot_seq_start,
	.next		= snapshot_seq_next,
	.stop		= snapshot_seq_stop,
	.show		= snapshot_seq_show,
};

/***************************************************************/
/********************* Snapshot buffer *************************/
/***************************************************************/
static struct list_head snapshot;	/* Head of the list */
#define COMM_MAX_LENGTH		256
struct process_list {
	int pid;
	char comm[COMM_MAX_LENGTH];
	struct list_head list;
};
/*
 * Fill/clean list
 */
static int get_snapshot(void);
static int clean_snapshot(void);
/*
 * Sort methods
 */
static int cmp_by_pid(void * , struct list_head * , struct list_head *);
static int cmp_by_pid_reverse(void * , struct list_head * , struct list_head *);
static int cmp_by_comm(void * , struct list_head * , struct list_head *);
static int cmp_by_comm_reverse(void * , struct list_head * ,
			       struct list_head *);
/*
 * Available commands to write in /proc/ file
 */
#define CMD_MAX_LENGTH		256
#define CMD_REFRESH		"refresh"
#define CMD_SORT_BY_PID		"straight"
#define CMD_SORT_BY_PID_REV	"reverse"
#define CMD_SORT_BY_COMM	"abc"
#define CMD_SORT_BY_COMM_REV	"zyx"

static int get_snapshot(void)
{
	struct task_struct *task = NULL;
	struct process_list *ps = NULL;
	print_start("get_snapshot");

	for_each_process(task) {
		ps = kmalloc(sizeof(struct process_list), GFP_KERNEL);
		if (!ps) {
			print_failed_exit("get_snapshot");
			return -ENOMEM;
		}
		ps->pid = task->pid;
		memcpy(ps->comm, task->comm, sizeof(task->comm));
		list_add_tail(&(ps->list), &snapshot);
	}

	print_exit("get_snapshot");
	return 0;
}

static int clean_snapshot(void)
{
	struct process_list *ps_list = NULL;
	struct list_head *ps = NULL;
	struct list_head *ps_safe = NULL;
	print_start("clean_snapshot");

	list_for_each_safe(ps, ps_safe, &snapshot) {
		ps_list = list_entry(ps, struct process_list, list);
		list_del(ps);
		kfree(ps_list);
	}

	print_exit("clean_snapshot");
	return 0;
}

static int cmp_by_pid(void *priv, struct list_head *a, struct list_head *b)
{
	struct process_list *ps_a = NULL;
	struct process_list *ps_b = NULL;

	ps_a = list_entry(a, struct process_list, list);
	ps_b = list_entry(b, struct process_list, list);

	return ps_a->pid - ps_b->pid;
}

static int cmp_by_pid_reverse(void *priv, struct list_head *a,
			      struct list_head *b)
{
	struct process_list *ps_a = NULL;
	struct process_list *ps_b = NULL;

	ps_a = list_entry(a, struct process_list, list);
	ps_b = list_entry(b, struct process_list, list);

	return ps_b->pid - ps_a->pid;
}

#define MIN_STR_LEN(_a, _b)					\
	({ int a_len = strlen(_a);				\
	   int b_len = strlen(_b);				\
	   (a_len < b_len) ? a_len : b_len; })
#define IGNORE_CASE(_ch)					\
	(((_ch > 0x40) && (_ch < 0x5B)) ? _ch + 0x20 : _ch)

static int cmp_by_comm(void *priv, struct list_head *a, struct list_head *b)
{
	struct process_list *ps_a = NULL;
	struct process_list *ps_b = NULL;
	int length = 0;
	int i = 0;
	int res = 0;

	ps_a = list_entry(a, struct process_list, list);
	ps_b = list_entry(b, struct process_list, list);
	length = MIN_STR_LEN(ps_a->comm, ps_b->comm);
	for (i = 0; i < length; i++) {
		res = IGNORE_CASE(ps_a->comm[i]) - IGNORE_CASE(ps_b->comm[i]);
		if (res != 0)
			break;
	}

	return res;
}

static int cmp_by_comm_reverse(void *priv, struct list_head *a,
			       struct list_head *b)
{
	struct process_list *ps_a = NULL;
	struct process_list *ps_b = NULL;
	int length = 0;
	int i = 0;
	int res = 0;

	ps_a = list_entry(a, struct process_list, list);
	ps_b = list_entry(b, struct process_list, list);
	length = MIN_STR_LEN(ps_a->comm, ps_b->comm);
	for (i = 0; i < length; i++) {
		res = IGNORE_CASE(ps_b->comm[i]) - IGNORE_CASE(ps_a->comm[i]);
		if (res != 0)
			break;
	}

	return res;
}

static int snapshot_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &snapshot_ops);
}

static void *snapshot_seq_start(struct seq_file *sf, loff_t *pos)
{
	/* Start reading from position */
	if (!sf->private)
		sf->private = seq_list_start(&snapshot, *pos);
	/* End of list */
	else if (sf->private == &snapshot)
		return NULL;

	return sf->private;
}

static void *snapshot_seq_next(struct seq_file *sf, void *v, loff_t *pos)
{
	(*pos)++;
	sf->private = ((struct list_head *)sf->private)->next;

	return NULL;
}

static void snapshot_seq_stop(struct seq_file *sf, void *v)
{
	return;
}

static int snapshot_seq_show(struct seq_file *sf, void *v)
{
	struct process_list *ps = NULL;

	ps = list_entry((struct list_head *)v, struct process_list, list);

	return seq_printf(sf, "<%d>: %s\n", ps->pid, ps->comm);
}

#define CMD_CMP(cmd)						\
	((cmd_buffer_size == sizeof(cmd)) &&			\
	 (strncmp(cmd_buffer, cmd, cmd_buffer_size - 1) == 0))
static ssize_t snapshot_write(struct file *file, const char __user *buffer,
			      unsigned long count, loff_t *data)
{
	char cmd_buffer[CMD_MAX_LENGTH];
	unsigned long cmd_buffer_size = count;
	int err = 0;
	print_start("snapshot_write");

	memset(cmd_buffer, '\0', CMD_MAX_LENGTH);

	/* Read from user space */
	if (cmd_buffer_size > CMD_MAX_LENGTH)
		cmd_buffer_size = CMD_MAX_LENGTH;
	if (copy_from_user(cmd_buffer, buffer, cmd_buffer_size)) {
		print_failed_exit("snapshot_write");
		return -EFAULT;
	}
	print_log("Snapshot was written: %s", cmd_buffer);
	/* Parse command */
	if (CMD_CMP(CMD_REFRESH)) {
		print_log("Refresh command");
		clean_snapshot();
		err = get_snapshot();
	} else if (CMD_CMP(CMD_SORT_BY_PID)) {
		print_log("Straight command");
		clean_snapshot();
		err = get_snapshot();
		list_sort(NULL, &snapshot, cmp_by_pid);
	} else if (CMD_CMP(CMD_SORT_BY_PID_REV)) {
		print_log("Reverse command");
		clean_snapshot();
		err = get_snapshot();
		list_sort(NULL, &snapshot, cmp_by_pid_reverse);
	} else if (CMD_CMP(CMD_SORT_BY_COMM)) {
		print_log("abc command");
		clean_snapshot();
		err = get_snapshot();
		list_sort(NULL, &snapshot, cmp_by_comm);
	} else if (CMD_CMP(CMD_SORT_BY_COMM_REV)) {
		print_log("zyx command");
		clean_snapshot();
		err = get_snapshot();
		list_sort(NULL, &snapshot, cmp_by_comm_reverse);
	} else {
		print_log("Undefined command");
	}

	if (err < 0) {
		print_failed_exit("snapshot_write");
		return err;
	}

	print_exit("snapshot_write");
	return cmd_buffer_size;
}

static int __init snapshot_init(void)
{
	int err = 0;
	print_start("snapshot_init");

	proc = proc_create(PROC_NAME, 0666, NULL, &proc_fops);
	if (!proc) {
		print_failed_exit("snapshot_init");
		return -EFAULT;
	}

	INIT_LIST_HEAD(&snapshot);
	err = get_snapshot();
	if (err != 0) {
		print_failed_exit("snapshot_init");
		return err;
	}

	print_exit("snapshot_init");
	return 0;
}

static void __exit snapshot_exit(void)
{
	print_start("snapshot_exit");

	clean_snapshot();
	remove_proc_entry(PROC_NAME, NULL);

	print_exit("snapshot_exit");
	return;
}

module_init(snapshot_init);
module_exit(snapshot_exit);

/***************************************************************/
/************************ Module info **************************/
/***************************************************************/
MODULE_AUTHOR("Petr Nechaev <petr@nechaev.us>");
MODULE_DESCRIPTION("Snapshot of currently executed processes");
MODULE_LICENSE("GPL");
MODULE_VERSION("2.2.3");
